package nachos.threads;

import nachos.machine.*;
import java.util.*;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm {
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
	Machine.timer().setInterruptHandler(new Runnable() {
		public void run() { timerInterrupt(); }
	    });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() {
		// wakeup threads that are ready

		long time = Machine.timer().getTime();

		// are any threads waiting?
		while(waitingThreads.peek() != null)
		{
			QueueEntry e = waitingThreads.peek();

			if(e.time <= time)
			{
				waitingThreads.poll(); // remove the item
				e.thread.ready(); // put the thread back on the ready queue
			}
			else
			{
				// we've went through enough of the queue
				break;
			}
		}

		KThread.currentThread().yield();
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
		boolean intStatus = Machine.interrupt().disable();
		waitingThreads.offer(new QueueEntry(Machine.timer().getTime() + x, KThread.currentThread()));
		KThread.currentThread().sleep();
		Machine.interrupt().restore(intStatus);
    }

	private static class Sleeper implements Runnable {
		long waitTime = -1;

		public Sleeper(long t)
		{
			waitTime = t;
		}

		public void run()
		{
			System.out.println("Sleeping for " + waitTime + " ticks");
			long before = Machine.timer().getTime();
			ThreadedKernel.alarm.waitUntil(waitTime);
			long realWait = Machine.timer().getTime() - before;
			long delta = realWait - waitTime;
			System.out.println("Real wait time: " + realWait + " delta: " + delta);
		}
	}

	public static void selfTest() {
		Lib.debug(dbgAlarm, "Enter Alarm Self Test");
				
		for(int i = 0; i < 10; i++)
		{
				long time = 1000 + (i*50);
				KThread thr = new KThread(new Sleeper(time));
				thr.setName("Sleeper-" + time);
				thr.fork();
		}
	}

	private class QueueEntry implements Comparable<QueueEntry>
	{
		public QueueEntry(long t, KThread k)
		{
			time = t;
			thread = k;
		}

		public int compareTo(QueueEntry e)
		{
			return (new Long(time)).compareTo(e.time);
		}

		public long time;
		public KThread thread;
	}
	
	PriorityQueue<QueueEntry> waitingThreads = new PriorityQueue<QueueEntry>();
	private static final char dbgAlarm = 'a';
}
